<?php

namespace yunj;

use think\facade\Request;
use yunj\enum\TipsTemplet;
use think\Validate as ThinkValidate;
use think\exception\HttpResponseException;

class Validate extends ThinkValidate {

    /**
     * 当前验证场景
     * 父类当中定义的格式类型为array是有问题的
     * @var string
     */
    protected $currentScene = null;

    /**
     * @var \Exception
     */
    protected $exception;

    /**
     * 自动处理，默认关闭
     * @var bool
     */
    protected $auto = false;

    /**
     * 原始验证数据
     * @var array
     */
    protected $raw_data;

    /**
     * 验证后的有效数据
     * @var array
     */
    protected $data;

    /**
     * 获取所有验证字段规则
     * @return array
     */
    public function getAttrRule(): array {
        return $this->rule;
    }

    /**
     * 设置所有验证字段规则
     * @param array $rule
     */
    public function setAttrRule(array $rule): void {
        $this->rule = $rule;
    }

    /**
     * 获取所有验证字段描述
     * @return array
     */
    public function getAttrField(): array {
        return $this->field;
    }

    /**
     * 设置所有验证字段描述
     * @param array $field
     */
    public function setAttrField(array $field): void {
        $this->field = $field;
    }

    /**
     * 获取所有验证环境
     * @return array
     */
    public function getAttrScene(): array {
        return $this->scene;
    }

    /**
     * 设置所有验证环境
     * @param array $scene
     */
    public function setAttrScene(array $scene): void {
        $this->scene = $scene;
    }

    /**
     * Notes: 获取错误提示
     * Author: Uncle-L
     * Date: 2021/11/13
     * Time: 13:44
     * @return string
     */
    public function getError(): string {
        $error = $this->error;
        return is_string($error) ? $error : implode(";", $error);
    }

    /**
     * Notes: 自动处理
     * Author: Uncle-L
     * Date: 2020/11/1
     * Time: 20:40
     * @param bool $is_enable [默认开启]
     * @return $this
     */
    public function auto(bool $is_enable = true) {
        $this->auto = $is_enable;
        return $this;
    }

    /**
     * Notes: 数据自动验证（重写，增加数据处理功能）
     * Author: Uncle-L
     * Date: 2020/11/1
     * Time: 20:49
     * @param array $data
     * @param array $rules
     * @param string $scene
     * @return bool
     */
    public function check($data, $rules = [], $scene = ''): bool {
        $this->raw_data = $data;
        $this->data = $data;
        // 验证结果
        $res = parent::check($data, $rules, $scene);
        if ($res) {
            try {
                // 验证通过，进行数据处理
                $handleAfterData = $this->handleData($this->raw_data, $this->currentScene);
                $this->data = $handleAfterData + $this->data;
                return $res;
            } catch (\Exception $e) {
                $this->exception = $e;
                // 进行数据处理有错误异常抛出
                if ($e instanceof HttpResponseException) {
                    $errorData = $e->getResponse()->getData();
                    if (isset($errorData["msg"])) $error = $errorData["msg"];
                }
                if (!isset($error)) $error = $e->getMessage();
                $this->error = $this->batch ? array_merge($this->error, [$error]) : $error;
                $res = false;
            }
        }
        // 验证失败，自动处理错误结果并响应
        if ($this->auto) {
            $msg = $this->getError();
            $msg = is_array($msg) ? implode('|', $msg) : $msg;
            Request::isAjax() ? throw_error_json($msg) : throw_redirect(url_tips(TipsTemplet::ERROR(), $msg));
        }
        return $res;
    }

    /**
     * Notes: 获取验证数据
     * Author: Uncle-L
     * Date: 2020/11/3
     * Time: 17:54
     * @return array
     */
    public function getData(): array {
        return $this->data;
    }

    /**
     * Notes: 获取异常
     * Author: Uncle-L
     * Date: 2020/11/3
     * Time: 17:54
     * @return \Exception
     */
    public function getException(): \Exception {
        return $this->exception;
    }

    /**
     * Notes: 可对校验的数据进行额外的处理、赋值等操作，并返回
     * Author: Uncle-L
     * Date: 2020/1/16
     * Time: 17:22
     * @param array $rawData [原始数据]
     * @param string|null $scene [验证场景]
     * @return array
     * @throws HttpResponseException|\Exception
     */
    protected function handleData(array $rawData, $scene): array {
        return [];
    }

    /**
     * Notes: 组装错误消息
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:23
     * @param string $field [字段名]
     * @param string $title [字段描述]
     * @param string $error [错误信息]
     * @return string
     */
    protected function packageError(string $field, string $title, string $error): string {
        return ($title ?: $field) . $error;
    }

    /**
     * Notes: 正整数验证
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:06
     * @param $value [验证数据]
     * @param string $rule [验证规则]
     * @param array $data [全部数据]
     * @param string $field [字段名]
     * @param string $title [字段描述]
     * @return bool|string
     */
    protected function positiveInteger($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
            return true;
        }
        return $this->packageError($field, $title, "需为正整数");
    }

    /**
     * Notes: 正整数验证
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:06
     * @param $value [验证数据]
     * @param string $rule [验证规则]
     * @param array $data [全部数据]
     * @param string $field [字段名]
     * @param string $title [字段描述]
     * @return bool|string
     */
    protected function positiveInt($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (is_positive_int($value)) return true;
        return $this->packageError($field, $title, "需为正整数");
    }

    /**
     * Notes: 非负整数验证
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:15
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function nonnegativeInteger($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (is_numeric($value) && is_int($value + 0) && ($value + 0) >= 0) {
            return true;
        }
        return $this->packageError($field, $title, "非负整数");
    }

    /**
     * Notes: 验证一维数组里面的值只能为rule的值
     * Author: Uncle-L
     * Date: 2020/2/16
     * Time: 23:02
     * @param $value [待验证数组]
     * @param string $rule [验证规则字符串]
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     * 使用实例：
     *          protected $rule = [
     *              'hobby' => 'array|arrayIn:read,write',
     *          ];
     *          protected $message = [
     *              'hobby.array' => '参数[hobby]格式错误',
     *              'hobby.arrayIn' => '参数[hobby]错误',
     *          ];
     */
    protected function arrayIn($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return $this->packageError($field, $title, "不能为空");
        if (!$rule || !strstr($rule, ',')) return $this->packageError($field, $title, "验证规则[arrayIn]错误");
        $ruleArr = explode(',', $rule);
        $res = array_in($value, $ruleArr);
        if (!$res) return $this->packageError($field, $title, "元素需在指定范围[{$rule}]内");
        return true;
    }

    /**
     * Notes: 验证一维数组为空或者里面的值只能为rule的值
     * Author: Uncle-L
     * Date: 2020/2/16
     * Time: 23:02
     * @param $value [待验证数组]
     * @param string $rule [验证规则字符串]
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     * 使用实例：
     *          protected $rule = [
     *              'hobby' => 'arrayEmptyOrIn:read,write',
     *          ];
     *          protected $message = [
     *              'hobby.arrayEmptyOrIn' => '参数[hobby]错误',
     *          ];
     */
    protected function arrayEmptyOrIn($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return true;
        if (!$rule || !strstr($rule, ',')) return $this->packageError($field, $title, "验证规则[arrayEmptyOrIn]错误");
        $ruleArr = explode(',', $rule);
        $res = array_in($value, $ruleArr);
        if (!$res) return $this->packageError($field, $title, "元素需在指定范围[{$rule}]内");
        return true;
    }

    /**
     * Notes: 验证一维数组里面的值只能为正整数
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:13
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function arrayPositiveInt($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return $this->packageError($field, $title, "不能为空");
        if (is_positive_int_array($value)) return true;
        return $this->packageError($field, $title, "需为正整数数组");
    }

    /**
     * Notes: 验证一维数组为空或值只能为正整数
     * Author: Uncle-L
     * Date: 2020/2/16
     * Time: 23:02
     * @param $value [待验证数组]
     * @param string $rule [验证规则字符串]
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     * 使用实例：
     *          protected $rule = [
     *              'hobby' => 'arrayEmptyOrPositiveInt',
     *          ];
     *          protected $message = [
     *              'hobby.arrayEmptyOrPositiveInt' => '参数[hobby]错误',
     *          ];
     */
    protected function arrayEmptyOrPositiveInt($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return true;
        if (is_positive_int_array($value)) return true;
        return $this->packageError($field, $title, "需为正整数数组");
    }

    /**
     * Notes: map数组的key必须包含给定的规则里面的值
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:13
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function mapHas($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return $this->packageError($field, $title, "不能为空");
        if (!$rule || !strstr($rule, ',')) return $this->packageError($field, $title, "验证规则[mapHas]错误");
        $ruleArr = explode(',', $rule);
        $valueKeys = array_keys($value);
        if (array_diff($ruleArr, $valueKeys)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: map数组的key必须包含给定的规则里面的值
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 16:13
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function mapEmptyOrHas($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_array($value)) return $this->packageError($field, $title, "格式错误");
        if (!$value) return true;
        if (!$rule || !strstr($rule, ',')) return $this->packageError($field, $title, "验证规则[mapHas]错误");
        $ruleArr = explode(',', $rule);
        $valueKeys = array_keys($value);
        if (array_diff($ruleArr, $valueKeys)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 验证地区数据有效性
     * Author: Uncle-L
     * Date: 2020/12/31
     * Time: 10:15
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function area($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        $accArr = ['province', 'city', 'district'];
        if (!$rule) $rule = "district";
        if (!in_array($rule, $accArr)) return $this->packageError($field, $title, "验证规则 area 允许参数[province|city|district]");
        $options = YunjArea::instance()->options();
        $acc = $rule;
        if (!is_array($value)) return $this->packageError($field, $title, "数据异常");

        if (!isset($value['province']) || !$value['province']) return $this->packageError($field, $title, "请选择省份");
        $provinceOptions = $options[0];
        if (!isset($provinceOptions[$value['province']])) return $this->packageError($field, $title, "省份数据异常");
        if ($acc === 'province') return true;

        if (!isset($value['city']) || !$value['city']) return $this->packageError($field, $title, "请选择城市");
        $cityOptions = $options["0,{$value['province']}"];
        if (!isset($cityOptions[$value['city']])) return $this->packageError($field, $title, "城市数据异常");
        if ($acc === 'city') return true;

        if (!isset($value['district']) || !$value['district']) return $this->packageError($field, $title, "请选择区/县");
        $cityOptions = $options["0,{$value['province']},{$value['city']}"];
        if (!isset($cityOptions[$value['district']])) return $this->packageError($field, $title, "区/县数据异常");

        return true;
    }

    /**
     * Notes: 验证汉字、字母、数字、下划线_、短横线-及空格
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function chsDashSpace($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        $res = is_scalar($value) && 1 === preg_match('/^[\x{4e00}-\x{9fa5}A-Za-z0-9\_\-\s]+$/u', (string)$value);
        if (!$res) return $this->packageError($field, $title, "只能是汉字、字母、数字、下划线_、短横线-及空格组合");
        return true;
    }

    /**
     * Notes: 验证16进制色号
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function hexColor($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        $res = is_scalar($value) && 1 === preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', (string)$value);
        if (!$res) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 验证时间日期
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function datetime($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        $time = strtotime($value);
        if (!$time) return $this->packageError($field, $title, "错误");
        if (date("s", $time) !== substr($value, -2)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 验证月份
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function month($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        $time = strtotime($value);
        if (!$time) return $this->packageError($field, $title, "错误");
        if (date("m", $time) !== substr($value, -2)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 验证时间
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function time($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        $value = "2021-11-26 {$value}";
        $time = strtotime($value);
        if (!$time) return $this->packageError($field, $title, "错误");
        if (date("s", $time) !== substr($value, -2)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 验证年份
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function year($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        $value .= "-01-01 00:00:01";
        $time = strtotime($value);
        if (!$time) return $this->packageError($field, $title, "错误");
        if (date("Y", $time) !== substr($value, 0, 4)) return $this->packageError($field, $title, "错误");
        return true;
    }

    /**
     * Notes: 逗号“,”间隔的汉字/字母/数字组合
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function commaIntervalChsAlphaNum($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        return preg_match("/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+(?:,[\x{4e00}-\x{9fa5}a-zA-Z0-9]+)*$/u", $value) ? true
            : $this->packageError($field, $title, "错误");
    }

    /**
     * Notes: 逗号“,”间隔的正整数
     * Author: Uncle-L
     * Date: 2020/12/20
     * Time: 17:27
     * @param $value
     * @param string $rule
     * @param array $data
     * @param string $field
     * @param string $title
     * @return bool|string
     */
    protected function commaIntervalPositiveInt($value, string $rule = "", array $data = [], string $field = "", string $title = "") {
        if (!is_string($value)) $this->packageError($field, $title, "错误");
        return preg_match("/^[1-9]\d*(?:,[1-9]\d*)*$/", $value) ? true
            : $this->packageError($field, $title, "错误");
    }


}